package netlink

import (
	"fmt"
	"os"
	"syscall"
)

type NetlinkProtocol int

const (
	CONNECTOR NetlinkProtocol = syscall.NETLINK_CONNECTOR
	INET_DIAG NetlinkProtocol = syscall.NETLINK_INET_DIAG
)

type NetlinkSocket struct {
	fd       int
	protocol NetlinkProtocol
	groups   uint32
}

func NewNetlinkSocket(protocol NetlinkProtocol, groups uint32) (*NetlinkSocket, error) {
    // 创建 netlink socket
	fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, int(protocol))
	if err != nil {
		return nil, err
	}
	ns := &NetlinkSocket{
		fd:       fd,
		protocol: protocol,
		groups:   groups,
	}
	if ns.protocol == CONNECTOR {
        // 构造地址（详细说明在下面）
		sockaddr := &syscall.SockaddrNetlink{
			Family: syscall.AF_NETLINK,
			Pid:    uint32(os.Getpid()),
			Groups: groups,
		}
        // 绑定地址
		if err = syscall.Bind(fd, sockaddr); err != nil {
			syscall.Close(fd)
			return nil, err
		}
	}
	return ns, nil
}

func (ns *NetlinkSocket) Close() {
	syscall.Close(ns.fd)
}

func (ns *NetlinkSocket) Send(request *NetlinkRequest) error {
    // 从上面的代码可以看到，对应 CONNECTOR 类型是已经绑定过地址的
	if ns.protocol == CONNECTOR {
		// FIXME: why write
		if _, err := syscall.Write(ns.fd, request.Serialize()); err != nil {
			return err
		}
	} else {
        // 在 syscall/syscall_linux.go 中定义
        //type SockaddrNetlink struct {
        //    Family uint16     /* AF_NETLINK */
        //    Pad    uint16     /* zero       */
        //    Pid    uint32     /* 1. 为消息发送进程的 pid */
        //                      /* 2. 若希望内核处理或者进行消息多播，则设置为 0 */
        //                      /* 3. 否则设置为处理消息的线程组 id */
        //                      /* 4. 在一个进程的多个线程使用 netlink socket 的情况下，进程中的 Pid 字段可以设置为其他值 */
        //                      /*    因此，该字段实际上未必是进程 ID ，其只是用于区分不同接收者或发送者的一个标识 */
        //                      /*    用户可以根据自己的需要设置该字段 */
        //    Groups uint32     /* 1. 用于指定多播组 */
        //                      /* 2. 通过 bind() 函数能够把调用进程加入到该字段指定的多播组中 */
        //                      /* 3. 若设置为 0 ，则表示不加入任何多播组 */
        //    raw    RawSockaddrNetlink
        //}
		sockaddr := &syscall.SockaddrNetlink{
			Family: syscall.AF_NETLINK,
			Pid:    0,
			Groups: ns.groups,
		}
        // FIXME: why sendto
		if err := syscall.Sendto(ns.fd, request.Serialize(), 0, sockaddr); err != nil {
			return err
		}
	}
	return nil
}

// 以 pagesize 大小为单位接收数据并转化成消息数组
func (s *NetlinkSocket) Receive() ([]syscall.NetlinkMessage, error) {
    // 获取运行时支持的 page 大小并创建 buffer
	rb := make([]byte, syscall.Getpagesize())
    // 从 socket 上获取数据保存到 buffer 中
	nr, _, err := syscall.Recvfrom(s.fd, rb, 0)
	if err != nil {
		return nil, err
	}
	if nr < syscall.NLMSG_HDRLEN {
		return nil, fmt.Errorf("Got short response from netlink")
	}
	rb = rb[:nr]
    // 在 syscall/netlink_linux.go 中
    // ParseNetlinkMessage parses b as an array of netlink messages
    // and returns the slice containing the NetlinkMessage structures.
	return syscall.ParseNetlinkMessage(rb)
}
